﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using System.Collections.Generic;
using Numbers.Transactions;

namespace Numbers.Arithmatic
{
    /// <summary>
    /// Multiply several guys together to get a result.
    /// </summary>
    public class Multiplication
    {
        private Operand _r;
        private Operand[] _mult;

        /// <summary>
        /// Creates a new addition relation between a result and the values.
        /// </summary>
        /// <param name="_lightcharm"></param>
        /// <param name="value"></param>
        public static void Create(Operand result, Operand[] addends)
        {
            new Multiplication(result, addends);
        }

        /// <summary>
        /// The c-tor that does the actual work.
        /// </summary>
        /// <param name="result"></param>
        /// <param name="addends"></param>
        public Multiplication(Operand result, Operand[] addends)
        {
            _r = result;
            _mult = addends;

            ///
            /// First, make sure everything is consistent!
            /// 

            double expected = CalculateMult();
            _r.v.Val = expected;
            if (_r.v.Val != expected)
            {
                throw new ArgumentException("Unable to make numbers multiply to the result - perhaps Max or Min value of the result have been violated?");
            }

            ///
            /// Now, wire up to monitor for changes
            /// 

            _r.v.ProposedPropertyChange += new Numbers.Transactions.ProposedPropertyChangeCallback(result_ProposedPropertyChange);

            foreach (var a in addends)
            {
                a.v.ProposedPropertyChange += new Numbers.Transactions.ProposedPropertyChangeCallback(a_ProposedPropertyChange);
            }

            ///
            /// Finally, make sure they are all part of the same transaction!
            /// 

            List<Value> allValues = new List<Value>();
            allValues.Add(result.v);
            foreach (var av in addends)
            {
                allValues.Add(av.v);
            }
            Manager.ManageByOneManager(allValues.ToArray());
        }

        /// <summary>
        /// Runs through everything and calculates the sum.
        /// </summary>
        /// <returns></returns>
        private double CalculateMult()
        {
            return _mult.Aggregate(1.0, (acc, v) => acc * v.v.Val);
        }

        /// <summary>
        /// One of the addons changed.
        ///  - If the result is fixed, we find someone else to adjust
        ///  - If the result is not fixed, then we just change that.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        bool a_ProposedPropertyChange(Numbers.Transactions.IObjectInTransaction sender, string propName)
        {
            double newVal = CalculateMult();
            if (!_r.isFixed)
            {
                _r.v.Val = newVal;
                return true;
            }
            else
            {
                return ChangeOperandToMakeWork(sender as Value);
            }
        }

        /// <summary>
        /// We can't change the result - but perhasp we can change another operand to balence things out.
        /// </summary>
        /// <param name="dontChangeAgain"></param>
        /// <returns></returns>
        private bool ChangeOperandToMakeWork(Value dontChangeAgain)
        {
            ///
            /// Simple case (including there being zeros on both sides).
            /// 

            double newVal = CalculateMult();
            if (_r.v.Val == newVal)
            {
                return true;
            }

            ///
            /// If the result is zero, this means one of the operands isn't. So we'll find to find one to make zero.
            /// 

            if (_r.v.Val == 0)
            {
                return ForceOperandToZero(dontChangeAgain);
            }

            ///
            /// If one (or more) of the operands is zero, then we'd better get going! If the
            /// value that was changed is zero, then we won't be able to fix this!
            /// 

            if (newVal == 0.0)
            {
                if (dontChangeAgain.Val == 0)
                {
                    return false;
                }
                return FixupZeroOperands();
            }

            ///
            /// The operands and result aren't zero. So we need to divy up the changes amongs the guys we can.
            /// 

            return FixupOperandsToMatch(dontChangeAgain, newVal);
        }

        /// <summary>
        /// We need to alter all the operands that we can such that things match.
        /// </summary>
        /// <param name="dontChangeAgain"></param>
        /// <returns></returns>
        private bool FixupOperandsToMatch(Value dontChangeAgain, double total)
        {
            Value.DisableNotifications[] changedMults;

            double factor = _r.v.Val / total;
            var guysToMod = from m in _mult
                            where !m.isFixed && m.v != dontChangeAgain
                            select m;

            if (guysToMod.Count() == 0)
            {
                return false;
            }
            else
            {
                double perFactorChange = Math.Pow(factor, 1 / ((double)guysToMod.Count()));
                changedMults = (from m in guysToMod
                                select new Value.DisableNotifications(m.v, m.v.Val * perFactorChange)).ToArray();
            }

            foreach (var a in changedMults)
            {
                a.Dispose();
            }
            return true;
        }

        /// <summary>
        /// The zero operands can't be zero any longer. Find them and fix them up.
        /// </summary>
        /// <returns></returns>
        private bool FixupZeroOperands()
        {
            Value.DisableNotifications[] changedMults;

            /// If thre are any fixed zero's then we are dead here!

            var fixedZeros = (from m in _mult
                              where m.isFixed && m.v.Val == 0
                              select m).Count();
            if (fixedZeros > 0)
            {
                return false;
            }

            /// Divide the changes among the zero guys...

            var zerosToMod = from m in _mult
                             where !m.isFixed && m.v.Val == 0
                             select m;
            double numZeros = zerosToMod.Count();
            if (numZeros == 0)
            {
                return false;
            }
            else
            {
                double nonZeroTotal = (from m in _mult
                                       where m.v.Val != 0
                                       select m).Aggregate(1.0, (a, mul) => a * mul.v.Val);
                double factor = _r.v.Val / nonZeroTotal;
                double newValueForZeros = Math.Pow(factor, 1 / numZeros);

                changedMults = (from m in zerosToMod
                                select new Value.DisableNotifications(m.v, newValueForZeros)).ToArray();
            }

            foreach (var a in changedMults)
            {
                a.Dispose();
            }

            return true;
        }

        /// <summary>
        /// Find some operand we can calter (besides the one that was already done) and set it to zeor.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private bool ForceOperandToZero(Value dontChangeAgain)
        {
            var canChange = from v in _mult
                            where !v.isFixed && v.v != dontChangeAgain
                            select v.v;
            Value first = canChange.FirstOrDefault();
            if (first == null)
            {
                return false;
            }
            first.Val = 0.0;
            return true;
        }

        /// <summary>
        /// The result changed. We need to back-propagate everything to
        /// the addends. A bit of a mess.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        bool result_ProposedPropertyChange(Numbers.Transactions.IObjectInTransaction obj, string propName)
        {
            return ChangeOperandToMakeWork(obj as Value);
            double total = CalculateMult();
            if (total == _r.v.Val)
            {
                return true;
            }

            Value.DisableNotifications[] changedMults;

            if (total == 0)
            {
                /// Divide the changes among the zero guys...

                var zerosToMod = from m in _mult
                                 where !m.isFixed && m.v.Val == 0
                                 select m;
                double numZeros = zerosToMod.Count();
                if (numZeros == 0)
                {
                    return false;
                }
                else
                {
                    double nonZeroTotal = (from m in _mult
                                           where m.v.Val != 0
                                           select m).Aggregate(1.0, (a, mul) => a * mul.v.Val);
                    double factor = _r.v.Val / nonZeroTotal;
                    double newValueForZeros = Math.Pow(factor, 1 / numZeros);

                    changedMults = (from m in zerosToMod
                                   select new Value.DisableNotifications(m.v, newValueForZeros)).ToArray();
                }
            }
            else
            {
                /// Everyone gets an equal share of things.

                double factor = _r.v.Val / total;
                var guysToMod = from m in _mult
                                where !m.isFixed
                                select m;

                if (guysToMod.Count() == 0)
                {
                    return false;
                }
                else
                {
                    double perFactorChange = Math.Pow(factor, 1 / ((double)guysToMod.Count()));
                    changedMults = (from m in guysToMod
                                    select new Value.DisableNotifications(m.v, m.v.Val*perFactorChange)).ToArray();
                }
            }


            ///
            /// Turn notifications back on. Hopefully we won't make it this far again!
            /// 
            foreach (var a in changedMults)
            {
                a.Dispose();
            }

            return true;
        }
    }
}
